﻿using System;
using System.Collections.Generic;
using BlobNet.Base.Common;
using BlobNet.Base.DataProviders;
using BlobNet.Base.Enums;
using BlobNet.Base.Interfaces;

namespace BlobNet.Base.Storage
{
    [Serializable]
    internal class PersistentStorage : StorageBase, IStorageManager
    {
        #region attributes

        //propertyName is the key


        #endregion

        #region Constructors

        internal PersistentStorage(Guid entityId, BlobDomain blobDomain)
            : base(entityId, blobDomain)
        {
            if (blobDomain == null)
                throw new ArgumentNullException("blobDomain");

        }

       

        #endregion Constructors

        #region Not Public Properties

        protected virtual IDataProvider DataProvider
        {
            get { return DataProviderFactory.Provider; }
        }

        #endregion Not Public Properties

        #region Public Methods

        public override bool HasStatusValue(string propertyName)
        {
            return DataProvider.HasPropertyValue(BlobDomain.EntityId,EntityId, propertyName);
        }

        public override void FillFrom(IStorageManager currentStorage)
        {
            Dictionary<string, object> source = ((MemoryStorage) currentStorage).Cache;

            foreach (var item in source)
            {
                SetStatusValue(item.Key, item.Value);
            }
        }

        public override TPropertyType GetStatusValue<TPropertyType>(string propertyName)
        {
            PropertyValueType propertyType = TypesHelper.GetPropertyValueType(typeof (TPropertyType));
            
            TPropertyType result;
            switch (propertyType)
            {
                case PropertyValueType.Enumeration:
                //case PropertyValueType.WrappedObject:
                case PropertyValueType.Value:
                    result = DataProvider.RetrievePropertyValue<TPropertyType>(BlobDomain.EntityId,EntityId, propertyName);
                    break;
                case PropertyValueType.Entity:
                    var value = DataProvider.RetrievePropertyValue<Guid>(BlobDomain.EntityId,EntityId, propertyName);
                    result = (TPropertyType)ObjectBuilder.Default.GetNewInstance(typeof(TPropertyType).FullName, BlobDomain, value);
                    break;
                case PropertyValueType.NotSupported:
                    throw new NotSupportedException();
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return result;
        }

        public override object SetStatusValue<TPropertyType>(string propertyName, TPropertyType value)
        {
            StorageOperationType operationType = StorageOperationType.Add;
            if (HasStatusValue(propertyName))
                operationType = StorageOperationType.Update;

            PropertyValueType propertyValueType = TypesHelper.GetPropertyValueType(value);
            object result;
            switch (propertyValueType)
            {
                case PropertyValueType.Enumeration:
                    object tmp = value;
                    StoreEnumValue(operationType, propertyName, (int) tmp,typeof(TPropertyType).FullName);
                    result = tmp;
                    break;

                case PropertyValueType.Value:
                    StorePropertyValue(operationType, propertyName, value);
                    result = value;
                    break;

                case PropertyValueType.Entity:
                    StoreEntity(operationType, propertyName, (BlobPart) (object) value);
                    result = value;
                    break;
              
                default:
                    throw new NotSupportedException();
            }

            return result;
        }

        public override bool CanDettach()
        {
            return !DataProvider.EntityIsReferenced(BlobDomain.EntityId, EntityId);
        }

        public override IList<IPersisteable> GetVariantSourcesItems()
        {
            throw new NotImplementedException();
        }

        public IList<string> GetRegisteredProperties()
        {
           IEnumerable<string> result=  DataProvider.RetrieveRegisteredProperties(BlobDomain.EntityId, EntityId);

            return new List<string>(result);
        }

        public List<TItemType> GetItems<TItemType>() where TItemType : BlobPart
        {
            List<TItemType> result=new List<TItemType>();
            List<Guid> ids = DataProvider.RetrieveItemsIds<TItemType>(BlobDomain.EntityId);
            foreach(var id in  ids)
            {
                object instance = ObjectBuilder.Default.GetNewInstance(typeof(TItemType).FullName, BlobDomain, id);
                //if (instance is TItemType) //TODO: improve query and remove this
                //{
                    var newInstance = (TItemType) instance;
                    result.Add(newInstance);
                //}
            }

            return result;
        }
        

        public override void ClearData()
        {
            DataProvider.RemovePropertyValues(BlobDomain.EntityId, EntityId);
        }

        #endregion Public Methods

        #region Not Public Methods

        private Guid StoreEntity(StorageOperationType operationType, string propertyName, BlobPart value)
        {
            if (value.Domain != BlobDomain)
                throw new InvalidOperationException("Cant store an BlobPart from another blobDomain");

            switch (operationType)
            {
                case StorageOperationType.Add:
                    DataProvider.AddPropertyValue<object>(BlobDomain.EntityId, EntityId, propertyName,
                                                          PropertyValueType.Entity, value.GetType().FullName,
                                                          value.EntityId);
                    break;
                case StorageOperationType.Update:
                    DataProvider.UpdatePropertyValue<object>(BlobDomain.EntityId, EntityId, propertyName,
                                                             PropertyValueType.Entity, value.GetType().FullName,
                                                             value.EntityId);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return value.EntityId;
        }

        private void StorePropertyValue(StorageOperationType operationType, string propertyName, object value)
        {
            switch (operationType)
            {
                case StorageOperationType.Add:
                    DataProvider.AddPropertyValue(BlobDomain.EntityId, EntityId, propertyName, PropertyValueType.Value, value.GetType().FullName,
                                                  value);
                    break;
                case StorageOperationType.Update:
                    DataProvider.UpdatePropertyValue(BlobDomain.EntityId, EntityId, propertyName, 
                                                     PropertyValueType.Value,value.GetType().FullName,
                                                     value);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void StoreEnumValue(StorageOperationType operationType, string propertyName, int value, string fullName)
        {
            switch (operationType)
            {
                case StorageOperationType.Add:
                    DataProvider.AddPropertyValue(BlobDomain.EntityId, EntityId, propertyName,
                                                  PropertyValueType.Enumeration,fullName,
                                                  value.ToString());
                    break;
                case StorageOperationType.Update:
                    DataProvider.UpdatePropertyValue(BlobDomain.EntityId, EntityId, propertyName,
                                                     PropertyValueType.Enumeration,fullName,
                                                     value.ToString());
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        #endregion Not Public Methods


        #region Implementation of IDisposable

        public void Dispose()
        {
            
        }

        #endregion

        public virtual void OnDeserialization(object sender)
        {
        }
    }
}